package idv.takeshi.software.productline.bookstore.domain.model.useraccount;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Transient;

import org.apache.commons.lang.Validate;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * A model class for representing as a User Account. 
 * @author takeshi
 *
 */
@Entity
@Inheritance(strategy=InheritanceType.JOINED)
@DiscriminatorColumn(name="USER_ACCOUNT_TYPE")
@Table(name = "USER_ACCOUNT")
@NamedQueries(
		{
			@NamedQuery(name = "findByActivationKey", query = "SELECT u FROM UserAccount u WHERE u.activationKey = :key"),
			@NamedQuery(name = "findByAccountName", query = "SELECT u FROM UserAccount u WHERE UPPER(u.accountName) = UPPER(:accountName)")}
	)
public abstract class UserAccount {
	
	private static final Log log = LogFactory.getLog(UserAccount.class);
	
	@Id
    @GeneratedValue(strategy = GenerationType.TABLE)
	private Long id;
	
	@Column(name = "USER_NAME", nullable = false, length = 20, unique = true)
	private String accountName;
	
	@Column(nullable = false)
	private String password;
	
	@Transient
	private String originalPassword;
	
	@Column(name = "ZIP_CODE", length = 10, nullable = false)
	private String zipCode;
	
	@Column(name = "CONTACT_ADDRESS", length = 50, nullable = false)
	private String contactAddress;
	
	@Column(name = "CONTACT_PHONE_NO", length = 20)
	private String contactPhoneNumber;
	
	@Column(name = "CELL_PHONE_NO", length = 20, nullable = false)
	private String cellPhoneNumber;
	
	@Column(name = "EMAIL_ADDRESS", length = 50, nullable = false)
	private String emailAddress;
	
	@Column(name = "NON_EXPIRED", nullable = false)
	private boolean nonExpired;
	
	@Column(name = "NON_LOCKED", nullable = false)
	private boolean nonLocked;
	
	@Column(name = "CREDENTIALS_NON_EXPIRED", nullable = false)
	private boolean credentialsNonExpired;
	
	@Column(name = "ENABLED", nullable = false)
	private boolean enabled;
	
	@Column(name = "ACTIVATION_KEY", nullable = false)
	private String activationKey;
	
	@Column(name = "ACTIVATION_EMAIL_SENT", nullable = false)
	private boolean activationEmailSent;
	
	@OneToMany(fetch = FetchType.EAGER, mappedBy = "userAccount", cascade = CascadeType.ALL)
	private List<Role> roles = new ArrayList<Role>();

	/**
	 * @return the id
	 */
	public Long getId() {
		return id;
	}

	/**
	 * @param id the id to set
	 */
	public void setId(Long id) {
		this.id = id;
	}

	/**
	 * @return the accountName
	 */
	public String getAccountName() {
		return accountName;
	}

	/**
	 * @param accountName the accountName to set
	 */
	public void setAccountName(String accountName) {
		this.accountName = StringUtils.lowerCase(accountName);
	}

	/**
	 * @return the password
	 */
	public String getPassword() {
		return password;
	}

	/**
	 * @param password the password to set
	 */
	public void setPassword(String password) {
		this.password = password;
	}

	/**
	 * @return the originalPassword
	 */
	public String getOriginalPassword() {
		return originalPassword;
	}

	/**
	 * @param originalPassword the originalPassword to set
	 */
	public void setOriginalPassword(String originalPassword) {
		this.originalPassword = originalPassword;
	}

	/**
	 * @return the zipCode
	 */
	public String getZipCode() {
		return zipCode;
	}

	/**
	 * @param zipCode the zipCode to set
	 */
	public void setZipCode(String zipCode) {
		this.zipCode = zipCode;
	}

	/**
	 * @return the contactAddress
	 */
	public String getContactAddress() {
		return contactAddress;
	}

	/**
	 * @param contactAddress the contactAddress to set
	 */
	public void setContactAddress(String contactAddress) {
		this.contactAddress = contactAddress;
	}

	/**
	 * @return the contactPhoneNumber
	 */
	public String getContactPhoneNumber() {
		return contactPhoneNumber;
	}

	/**
	 * @param contactPhoneNumber the contactPhoneNumber to set
	 */
	public void setContactPhoneNumber(String contactPhoneNumber) {
		this.contactPhoneNumber = contactPhoneNumber;
	}

	/**
	 * @return the cellPhoneNumber
	 */
	public String getCellPhoneNumber() {
		return cellPhoneNumber;
	}

	/**
	 * @param cellPhoneNumber the cellPhoneNumber to set
	 */
	public void setCellPhoneNumber(String cellPhoneNumber) {
		this.cellPhoneNumber = cellPhoneNumber;
	}

	/**
	 * @return the emailAddress
	 */
	public String getEmailAddress() {
		return emailAddress;
	}

	/**
	 * @param emailAddress the emailAddress to set
	 */
	public void setEmailAddress(String emailAddress) {
		this.emailAddress = emailAddress;
	}

	/**
	 * @return the nonExpired
	 */
	public boolean isNonExpired() {
		return nonExpired;
	}

	/**
	 * @param nonExpired the nonExpired to set
	 */
	public void setNonExpired(boolean nonExpired) {
		this.nonExpired = nonExpired;
	}

	/**
	 * @return the nonLocked
	 */
	public boolean isNonLocked() {
		return nonLocked;
	}

	/**
	 * @param nonLocked the nonLocked to set
	 */
	public void setNonLocked(boolean nonLocked) {
		this.nonLocked = nonLocked;
	}

	/**
	 * @return the credentialsNonExpired
	 */
	public boolean isCredentialsNonExpired() {
		return credentialsNonExpired;
	}

	/**
	 * @param credentialsNonExpired the credentialsNonExpired to set
	 */
	public void setCredentialsNonExpired(boolean credentialsNonExpired) {
		this.credentialsNonExpired = credentialsNonExpired;
	}

	/**
	 * @return the enabled
	 */
	public boolean isEnabled() {
		return enabled;
	}

	/**
	 * @param enabled the enabled to set
	 */
	public void setEnabled(boolean enabled) {
		this.enabled = enabled;
	}

	/**
	 * @return the activationKey
	 */
	public String getActivationKey() {
		return activationKey;
	}

	/**
	 * @param activationKey the activationKey to set
	 */
	public void setActivationKey(String activationKey) {
		this.activationKey = activationKey;
	}

	/**
	 * @return the activationEmailSent
	 */
	public boolean isActivationEmailSent() {
		return activationEmailSent;
	}

	/**
	 * @param activationEmailSent the activationEmailSent to set
	 */
	public void setActivationEmailSent(boolean activationEmailSent) {
		this.activationEmailSent = activationEmailSent;
	}
	
	/**
	 * Unmodifiable list of roles.
	 * @return the roles
	 */
	public List<Role> getRoles() {
		return Collections.unmodifiableList(roles);
	}

	/**
	 * Get a specific role by given roleClazz.
	 * @param <T>
	 * @param roleClazz
	 * @return A role, may be null.
	 */
	public <T extends Role> Role getRole(Class<T> roleClazz){
		Role foundRole = null;
		Validate.notNull(roleClazz, "given role class shall always not be null");
		if(!this.roles.isEmpty()){
			Role role = null;
			for(Iterator<Role> it = this.roles.iterator(); it.hasNext();){
				role = it.next();
				if(roleClazz.equals(role.getClass())){
					foundRole = role;
					break;
				}
			}
		}
		return foundRole;
	}
	
	/**
	 * Check whether has the role by given roleClazz.
	 * @param <T>
	 * @param roleClazz
	 * @return True if found.
	 */
	public <T extends Role> boolean hasRole(Class<T> roleClazz){
		return null != this.getRole(roleClazz)? true: false;
	}

	/**
	 * Add new role for this UserAccount.
	 * @return the roles
	 * @throws CreateRoleFailedException 
	 */
	public <T extends Role> void addRole(Class<T> roleClazz) throws RoleDuplicateException {
		Validate.notNull(roleClazz, "role class shall always not be null");
		Role role = null;
		if(this.hasRole(roleClazz))
			throw new RoleDuplicateException("role:" + roleClazz + " is duplicate");
		try {
			role = roleClazz.newInstance();
//			role.setId(generateNewRoleId());
			
			this.roles.add(role);
			role.setUserAccount(this);
		} catch (Exception e) {
			String msg = "create role for roleClazz:" + roleClazz + " failed";
			log.error(msg, e);
			throw new CreateRoleFailedException(msg, e);
		}
	}
	
	/* (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (obj == null) { return false; }
		   if (obj == this) { return true; }
		   if (obj.getClass() != getClass()) {
		     return false;
		   }
		   UserAccount rhs = (UserAccount) obj;
		   return new EqualsBuilder()
			   .append(this.id, rhs.id)
			   .append(this.accountName, rhs.accountName)
			   .append(this.password, rhs.password)
			   .append(this.originalPassword, rhs.originalPassword)
			   .append(this.zipCode, rhs.zipCode)
			   .append(this.contactAddress, rhs.contactAddress)
			   .append(this.contactPhoneNumber, rhs.contactPhoneNumber)
			   .append(this.cellPhoneNumber, rhs.cellPhoneNumber)
			   .append(this.emailAddress, rhs.emailAddress)
			   .append(this.nonExpired, rhs.nonExpired)
			   .append(this.nonLocked, rhs.nonLocked)
			   .append(this.credentialsNonExpired, rhs.credentialsNonExpired)
			   .append(this.enabled, rhs.enabled)
			   .append(this.activationKey, rhs.activationKey)
			   .append(this.activationEmailSent, rhs.activationEmailSent)
			   .append(this.roles, rhs.roles)
		       .isEquals();
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		return new HashCodeBuilder(17, 37).
	       append(this.id).
	       append(this.accountName).
	       append(this.password).
	       append(this.originalPassword).
	       append(this.zipCode).
	       append(this.contactAddress).
	       append(this.contactPhoneNumber).
	       append(this.cellPhoneNumber).
	       append(this.emailAddress).
	       append(this.nonExpired).
	       append(this.nonLocked).
	       append(this.credentialsNonExpired).
	       append(this.enabled).
	       append(this.activationKey).
	       append(this.activationEmailSent).
	       append(this.roles).
	       toHashCode();
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return ReflectionToStringBuilder.toString(this, ToStringStyle.MULTI_LINE_STYLE);
	}

	private Integer generateNewRoleId(){
		Integer newId = new Integer("1");
		
		if(!this.roles.isEmpty()){
			Integer maxId = null;
			for(Role role : this.roles){
				if(null == maxId){
					maxId = role.getId();
				} else{
					if(maxId.compareTo(role.getId()) < 0){
						maxId = role.getId();
					}
				}
			}
			newId = maxId + 1;
		}
		
		return newId;
	}
	 
}
 
